#include "../../Helix.h"
#include "../../HObject.h"
#include "../../Log.h"
#include "Image.h"
#include "TGAImage.h"
#include <string>
#include <iostream>
#include <fstream>
#include <vector>

using namespace std;

namespace helix
{
	
	TGAImage::TGAImage(const char *fileName) : Image(fileName)
	{
		string fName = fileName;
		loaded = loadTGA(&fName);
		depth = 1;		//TGA images are only 2Dimensional

		if(loaded)
		{
			imageType = "TGA";
			//TGA images are stored in BGR order, we need our data to be RGB
			if(channels == 4 || channels == 3)
				swapPixelComponents(Image::RED_CHANNEL,Image::BLUE_CHANNEL);	//swap the first and third component values of every pixel
		}
	}

	TGAImage::~TGAImage()
	{
		if(data!=NULL)
			delete data;
	}

	bool TGAImage::loadTGA(string *fileName)
	{
		TGAHeader tgaH;		//the file header

		//Open the file for binary reading
		ifstream fileIn(fileName->data(),ifstream::binary);

		//Make sure the file is open
		if(!fileIn.is_open())
		{
			Log().Error("File: %s did not open correctly. ",fileName->data());
			return false;
		}
		
		
		//read in the TGAHeader (18 bytes)
		char header[18];
		fileIn.read((char*)&header, 18);

		setHeaderFromByteArray(&tgaH,(unsigned char*)header);

		//The size of the array we need to create is 
		//width * height * bpp/8
		int bytesPerPixel = tgaH.PixelDepth / 8;

		if(bytesPerPixel > 4)
		{
			Log().Error("TGA pixel depth (%d) is not supported.",tgaH.PixelDepth);
		}

		int size = tgaH.Height * tgaH.Width * bytesPerPixel;

		data = new vector<unsigned char>(size);

		bool loadedOK = false;

		switch(tgaH.ImageType)
		{
			case TRUE_COLOR_NON_ENCODED:
				loadedOK = loadTrueColorNonEncoded(tgaH,&fileIn);
				break;
			case TRUE_COLOR_ENCODED:
				loadedOK = loadTrueColorEncoded(tgaH,&fileIn);
				break;
			default:
				Log().Error("TGA image type (%d) is not supported for file %s.",tgaH.ImageType,fileName);
				break;
		}

		//If the file failed to load, close the file and exit.
		if ( !loadedOK )
		{
			Log().Error("There was an error loading image %s",fileName);
			fileIn.close();
			return false;
		}

		bpp=tgaH.PixelDepth;
		width=tgaH.Width;
		height=tgaH.Height;
		channels = bpp / 8;

		//Close the file
		fileIn.close();
		return true;

	}

	bool TGAImage::loadTrueColorNonEncoded(TGAHeader tgaH, ifstream *fileIn)
	{
		moveFileToImageData(tgaH,fileIn);

		//The amount of data to read in from the file
		int bytesPerPixel = tgaH.PixelDepth / 8;
		int size = tgaH.Height * tgaH.Width * bytesPerPixel;

		fileIn->read((char*)&(*data)[0],size);

		return true;
	}

	bool TGAImage::loadTrueColorEncoded(TGAHeader tgaH, ifstream *fileIn)
	{
		moveFileToImageData(tgaH,fileIn);

		//The amount of data to read in from the file
		int bytesPerPixel = tgaH.PixelDepth / 8;
		int size = tgaH.Height * tgaH.Width * bytesPerPixel;

		int currentByte = 0;

		while(currentByte < size)
		{
			//First, read in a packet header byte
			unsigned char packetHead;
			fileIn->read((char*)&packetHead,1);

			//Determine if it is RLE encoded, or RAW (non-RLE)
			unsigned char packetType = packetHead & RLE_PACKET_TYPE_MASK;
			unsigned char packetLength = (packetHead & RLE_PACKET_MASK) + 1;

			//The ammount of pixels the header corresponds to
			int readSize = 1;				//amount to read from file
			int dataSize = packetLength;	//amount to set in array

			switch(packetType)
			{
				case RLE_PACKET:	//If it is RLE encoded	
						readSize = 1;
					break;
				case NON_RLE_PACKET:	//If it is RAW encoded
						readSize = packetLength;
					break;
			}

			int byteCount = bytesPerPixel * readSize;

			//Create a temporary array of the pixel values to read in
			unsigned char *readBytes = new unsigned char[byteCount];
			
			fileIn->read((char*)readBytes,byteCount);

			//Set all the bytes that need to be set in the array
			for(int i=0;i<dataSize * bytesPerPixel;i++)
			{
				//We have a different ammount of data to read depending on
				//if the packet type is raw or not
				switch(packetType)
				{
					case RLE_PACKET:
                        (*data)[currentByte] = readBytes[i%bytesPerPixel];	//The same pixel data is stored for all pixels
						break;
					case NON_RLE_PACKET:
                        (*data)[currentByte] = readBytes[i];	//Each pixel's data is stored and could be different
						break;
				}
				currentByte++;
			}

			delete[] readBytes;
		}

		return true;
	}

	void TGAImage::moveFileToImageData(TGAHeader tgaH, ifstream *fileIn)
	{
		//Move the file pointer to the start of image data
		// (headersize) + (id length) + (colormap length)
		int dataStart = 18 + tgaH.IDLength + tgaH.CMapLength;
		fileIn->seekg(dataStart, ios_base::beg);
	}

	void TGAImage::setHeaderFromByteArray(TGAHeader *tgaH, unsigned char *header)
	{
		tgaH->IDLength = header[0];        
		tgaH->ColorMapType = header[1];
		tgaH->ImageType = header[2];
		tgaH->CMapStart = header[3]+ (header[4]<<8);
		tgaH->CMapLength = header[5]+ (header[6]<<8);
		tgaH->CMapDepth = header[7];
		tgaH->XOffset = header[8]+ (header[9]<<8);
		tgaH->YOffset = header[10]+ (header[11]<<8); 
		tgaH->Width = header[12]+ (header[13]<<8);
		tgaH->Height  = header[14]+ (header[15]<<8);
		tgaH->PixelDepth = header[16];
		tgaH->ImageDescriptor = header[17];
	}
}



